﻿using System;
using System.Xml.Linq;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.IdentityModel.Tokens;
using System.ServiceModel.Security.Tokens;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using SecurityTokenServiceNS;

namespace RstAddIns
{
	public class RSTModule0502 : IRSTProcessor
    {
		private XNamespace _wst = Constants.WSTrust.NamespaceUri0502;

        /// <summary>
        /// This method processes the received RST message into a RST object
        /// </summary>
        /// <param name="rst">RST Object</param>
        /// <param name="rst">RST Message</param>
        public void ProcessMessage(RST rst, XElement xRst)
        {
            try
            {
				if (rst.NamespaceUri != _wst)
					return;

                if (xRst == null)
                    throw new ArgumentNullException("xRst");

                if (xRst.IsEmpty)
                    throw new ArgumentException("wst:RequestSecurityToken element was empty. Unable to process RST Object.");

				rst.Context = xRst.Attribute(_wst + Constants.WSTrust.Attributes.Context) != null ?
					xRst.Attribute(_wst + Constants.WSTrust.Attributes.Context).ToString() : String.Empty;

				rst.RequestType = ProcessStrElement(xRst, _wst, Constants.WSTrust.Elements.RequestType, String.Empty);
				rst.TokenType = ProcessStrElement(xRst, _wst, Constants.WSTrust.Elements.TokenType, String.Empty);
				rst.KeyType = ProcessStrElement(xRst, _wst, Constants.WSTrust.Elements.KeyType, String.Empty);
				rst.EncryptWith = ProcessStrElement(xRst, _wst, Constants.WSTrust.Elements.EncryptWith, String.Empty);
				rst.SignWith = ProcessStrElement(xRst, _wst, Constants.WSTrust.Elements.SignWith, String.Empty);
				rst.CanonicalizationAlgorithm = ProcessStrElement(xRst, _wst, Constants.WSTrust.Elements.CanonicalizationAlgorithm, String.Empty);
				rst.EncryptionAlgorithm = ProcessStrElement(xRst, _wst, Constants.WSTrust.Elements.EncryptionAlgorithm, String.Empty);
				rst.ComputedKeyAlgorithm = ProcessStrElement(xRst, _wst, Constants.WSTrust.Elements.ComputedKeyAlgorithm, String.Empty);

				rst.KeySize = xRst.Element(_wst + Constants.WSTrust.Elements.KeySize) != null ?
					Convert.ToInt16(xRst.Element(_wst + Constants.WSTrust.Elements.KeySize).Value) : 0;

                rst.AppliesTo = ProcessAppliesTo(
                    xRst.Element((XNamespace)Constants.WSPolicy.NamespaceUri + Constants.WSPolicy.Elements.AppliesTo));

				rst.Entropy = ProcessEntropy(xRst.Element(_wst + Constants.WSTrust.Elements.Entropy));

				rst.Claims = ProcessClaims(xRst.Element(_wst + Constants.WSTrust.Elements.Claims));

				rst.UseKey = ProcessUseKey(xRst.Element(_wst + Constants.WSTrust.Elements.UseKey));

                rst.ProofType =
					rst.KeyType == Constants.WSTrust.KeyTypes0502.Public ? Constants.WSTrust.ProofType.PublicKey :
					rst.KeyType == Constants.WSTrust.KeyTypes0502.Symmetric ? Constants.WSTrust.ProofType.SymmetricKey : 
					Constants.WSTrust.ProofType.Bearer;
            }
            catch (Exception Ex)
            {
                Console.WriteLine(Ex.Message);
                throw Ex;
            }
        }

        private string ProcessStrElement(XElement xe, XNamespace ns, string elemName, string defaultStr)
        {
            return xe.Element(ns + elemName) != null ? xe.Element(ns + elemName).Value : defaultStr;
        }

        private EndpointAddress ProcessAppliesTo(XElement xAppliesTo)
        {
            if (xAppliesTo != null)
            {
                if (xAppliesTo.IsEmpty)
                    throw new ArgumentException("wsp:AppliesTo element was empty. Unable to create EndpointAddress object");

                XElement epr = null;

				if ((epr = xAppliesTo.Element((XNamespace)Constants.WSAddressing.NamespaceUri10 +
					Constants.WSAddressing.Elements.EndpointReference)) != null)
				{
					DataContractSerializer dcs = new DataContractSerializer(typeof(EndpointAddress10));
					return ((EndpointAddress10)dcs.ReadObject(epr.CreateReader())).ToEndpointAddress();
				}
				else if ((epr = xAppliesTo.Element((XNamespace)Constants.WSAddressing.NamespaceUri0408 +
					Constants.WSAddressing.Elements.EndpointReference)) != null)
				{
					DataContractSerializer dcs = new DataContractSerializer(typeof(EndpointAddressAugust2004));
					return ((EndpointAddressAugust2004)dcs.ReadObject(epr.CreateReader())).ToEndpointAddress();
				}
            }
            return null;
        }

        private SecurityToken ProcessEntropy(XElement xEntropy)
        {
            if (xEntropy != null)
            {
                if (xEntropy.IsEmpty)
                    throw new ArgumentException("wst:Entropy element was empty. Unable to create SecurityToken object");

                XElement bs = null;
                if ((bs = xEntropy.Element(_wst + Constants.WSTrust.Elements.BinarySecret)) != null)
                    return new BinarySecretSecurityToken(Convert.FromBase64String(bs.Value));
            }
            return null;
        }

        private IList<ClaimTypeRequirement> ProcessClaims(XElement xClaims)
        {
            if (xClaims != null)
            {
                if (xClaims.IsEmpty)
                    throw new ArgumentException("wst:Claims element was empty. Unable to create claim requirement list");

                List<ClaimTypeRequirement> claims = new List<ClaimTypeRequirement>();
                string claimType, optStr = null;
                bool optional;

                foreach (XElement xe in xClaims.Elements())
                {
                    if (xe.GetPrefixOfNamespace(Constants.WSIdentity.NamespaceUri) != null &&
						xe.Attribute(Constants.WSIdentity.Attributes.Uri) != null)
                    {
						claimType = xe.Attribute(Constants.WSIdentity.Attributes.Uri).Value;
						if (xe.Attribute(Constants.WSIdentity.Attributes.Optional) != null)
							optStr = xe.Attribute(Constants.WSIdentity.Attributes.Optional).ToString();
                        optional = optStr != null && (optStr == "true" || optStr == "True");
                        claims.Add(new ClaimTypeRequirement(claimType, optional));
                    }
                }
                return claims;
            }
            return null;
        }

        private SecurityToken ProcessUseKey(XElement xUseKey)
        {
            if (xUseKey != null)
            {
                if (xUseKey.IsEmpty)
                    throw new ArgumentException("wst:UseKey element was empty. Unable to create SecurityToken object");
				return ProcessKeyInfo(xUseKey.Element(
					(XNamespace)Constants.XMLSignature.NamespaceUri + Constants.XMLSignature.Elements.KeyInfo));
            }
            return null;
        }

        private SecurityToken ProcessKeyInfo(XElement xKeyInfo)
        {
            if (xKeyInfo != null)
            {
                if (xKeyInfo.IsEmpty)
                    throw new ArgumentException("ds:KeyInfo element was empty. Unable to create SecurityToken object");
				return ProcessKeyValue(xKeyInfo.Element(
					(XNamespace)Constants.XMLSignature.NamespaceUri + Constants.XMLSignature.Elements.KeyValue));
            }
            return null;
        }

        private SecurityToken ProcessKeyValue(XElement xKeyValue)
        {
            if (xKeyValue != null)
            {
                if (xKeyValue.IsEmpty)
                    throw new ArgumentException("ds:KeyValue element was empty. Unable to create SecurityToken object");
				return ProcessRsaKeyValue(xKeyValue.Element(
					(XNamespace)Constants.XMLSignature.NamespaceUri + Constants.XMLSignature.Elements.RsaKeyValue));
            }
            return null;
        }

        private SecurityToken ProcessRsaKeyValue(XElement xRsaKeyValue)
        {
            if (xRsaKeyValue != null)
            {
                if (xRsaKeyValue.IsEmpty)
                    throw new ArgumentException("ds:RsaKeyValue element was empty. Unable to create SecurityToken object");

                RSA key = RSA.Create();
                key.FromXmlString(xRsaKeyValue.ToString());
                return new RsaSecurityToken(key);
            }
            return null;
        }
    }
}
